package nagel_schreckenberg;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

import nagel_schreckenberg.Lane.Direction;
import nagel_schreckenberg.Lane.Occupation;
public class FourIntersection implements Intersection{

	private Lane northIn;
	private Lane northOut;
	private Lane eastIn;
	private Lane eastOut;
	private Lane southIn;
	private Lane southOut;
	private Lane westIn;
	private Lane westOut;
	
	private PhasePolicy p;
	private ArrayList<Lane> allLanes;
	private ArrayList<Lane> inLanes;
	
	private int phasecounter;
	public enum Phase{
		NORTH, EAST, SOUTH, WEST;
	};
	private Phase phase;
	
	private ArrayList<PartialModel> avpm;
	private ArrayList<State> history;
	private ArrayList<State> allStates;
	private PartialModel mcur;
	private State s;
	private int roundcounter;
	private String filename;
	
	public FourIntersection(){
		allLanes = new ArrayList<Lane>();
		inLanes = new ArrayList<Lane>();
		avpm = new ArrayList<PartialModel>();
		history = new ArrayList<State>();
		allStates = createAllStates();
		roundcounter = 0;
	}
	
	public FourIntersection(String id){
		allLanes = new ArrayList<Lane>();
		inLanes = new ArrayList<Lane>();
		avpm = new ArrayList<PartialModel>();
		history = new ArrayList<State>();
		allStates = createAllStates();
		roundcounter = 0;
		filename = "learning"+id+".pm";
	}
	
	public ArrayList<State> createAllStates(){
		ArrayList<Occupation> occs = new ArrayList<Occupation>();
		occs.add(Occupation.EMPTY);
		occs.add(Occupation.REGULAR);
		occs.add(Occupation.FULL);
		ArrayList<State> states = new ArrayList<State>();
		for(Occupation no : occs){
			for(Occupation eo : occs){
				for(Occupation so : occs){
					for(Occupation wo : occs){
						states.add(new State(no, eo, so, wo));
					}
				}
			}
		}
		return states;
	}
	
	@Override
	public void controlLights(){
		if(roundcounter == 0){
			p = new PhasePolicy(Config.GREENPOLICY);
		}
		if(phasecounter >= p.getGreenTime()){
			phasecounter = 0;
			if(phase == Phase.NORTH){
				phase = Phase.EAST; northIn.red(); eastIn.green();
			}
			else if(phase == Phase.EAST){
				phase = Phase.SOUTH; eastIn.red(); southIn.green();
			}
			else if(phase == Phase.SOUTH){
				phase = Phase.WEST; southIn.red(); westIn.green();
			}
			else if(phase == Phase.WEST){
				phase = Phase.NORTH; westIn.red(); northIn.green();
			}
			else{
				System.out.println("could not switch phase");
			}
		}
		else{
			phasecounter++;
		}
		roundcounter++;
	}
	
	public void addLanes(Lane ni, Lane no, Lane ei, Lane eo, Lane si, Lane so, Lane wi, Lane wo) throws Exception{
		northIn = ni;
		northIn.connectTarget(this);
		allLanes.add(northIn);
		inLanes.add(northIn);
		northOut = no;
		northOut.connectSource(this);
		allLanes.add(northOut);
		eastIn = ei;
		eastIn.connectTarget(this);
		allLanes.add(eastIn);
		inLanes.add(eastIn);
		eastOut = eo;
		eastOut.connectSource(this);
		allLanes.add(eastOut);
		southIn = si;
		southIn.connectTarget(this);
		allLanes.add(southIn);
		inLanes.add(southIn);
		southOut = so;
		southOut.connectSource(this);
		allLanes.add(southOut);
		westIn = wi;
		westIn.connectTarget(this);
		allLanes.add(westIn);
		inLanes.add(westIn);
		westOut = wo;
		westOut.connectSource(this);
		allLanes.add(westOut);
		
		phasecounter = 0;
		phase = Phase.NORTH;
		northIn.green();
		northIn.setOccupation();
		eastIn.red();
		eastIn.setOccupation();
		southIn.red();
		southIn.setOccupation();
		westIn.red();
		westIn.setOccupation();
		
		mcur = new PartialModel(allStates);
		avpm.add(mcur);
		s = new State(northIn.getOccupation(), eastIn.getOccupation(), southIn.getOccupation(), westIn.getOccupation());
		history.add(s);
		mcur.setPolicy(s.optimal());
		p = mcur.getPolicy();
	}

	@Override
	public void clearSink() {}

	@Override
	public Lane getIn(Direction d) {
		Lane l = null;
		if(d == Direction.NORTH){
			l = southIn;
		}
		else if(d == Direction.EAST){
			l = westIn;
		}
		else if(d == Direction.SOUTH){
			l = northIn;
		}
		else if(d == Direction.WEST){
			l = eastIn;
		}
		else{
			System.out.println("correct outbound lane not found");
		}
		return l;
	}
	
	@Override
	public Lane getOut(Direction d) {
		Lane l = null;
		if(d == Direction.NORTH){
			l = southOut;
		}
		else if(d == Direction.EAST){
			l = westOut;
		}
		else if(d == Direction.SOUTH){
			l = northOut;
		}
		else if(d == Direction.WEST){
			l = eastOut;
		}
		else{
			System.out.println("correct outbound lane not found");
		}
		return l;
	}

	@Override
	public void spawn() {}

	@Override
	public boolean hasSpawned() {return false;}
	
	@Override
	public void gershenson() { 
		//TODO rules 5 & 6 still to implement
		//TODO include CONTEXTINFO
		boolean sw = false;
		Lane next = null;
		for(Lane l : inLanes){
			if(l.getPhase()){
				next = l;
			}
		}
		int waitingCars = 0;
		
		if(phasecounter < Config.GERSHENSON_U){
			for(Lane l : inLanes){
				if(l.getPhase()){
					if(l.waitingWithin(Config.GERSHENSON_D) == 0){
						sw = true;
					}
				}
				if(!l.getPhase()){
					if(l.waitingWithin(Config.GERSHENSON_D) > waitingCars){
						waitingCars = l.waitingWithin(Config.GERSHENSON_D);
						next = l;
					}
				}
			}
		}
		else{
			for(Lane l : inLanes){
				if(!l.getPhase()){
					if(l.waitingWithin(Config.GERSHENSON_D) > waitingCars){
						waitingCars = l.waitingWithin(Config.GERSHENSON_D);
						next = l;
						sw = true;
					}
				}
			}
			for(Lane l : inLanes){
				if(l.getPhase()){
					if(l.waitingWithin(Config.GERSHENSON_R) <= Config.GERSHENSON_M && l.waitingWithin(Config.GERSHENSON_R) > 0){
						sw = false;
					}
				}
			}
		}
		if(sw){
			for(Lane l : inLanes){
				if(l.getPhase()){
					l.red();
				}
			}
			next.green();
			phasecounter = 0;
		}
		else{
			phasecounter++;
		}
	}

	@Override
	public int countSink() {return 0;}

	@Override
	public double travelTime() {return 0;}

	@Override
	public void learning() {		
		//part one: control traffic lights according to current model
		int greentime = 0;
		if(phase == Phase.NORTH){
			greentime = mcur.getPolicy().getNorth();
		}
		else if(phase == Phase.EAST){
			greentime = mcur.getPolicy().getEast();
		}
		else if(phase == Phase.SOUTH){
			greentime = mcur.getPolicy().getSouth();
		}
		else{
			greentime = mcur.getPolicy().getWest();
		}
		
		if(phasecounter >= greentime){
			phasecounter = 0;
			if(phase == Phase.NORTH){
				phase = Phase.EAST; northIn.red(); eastIn.green();
			}
			else if(phase == Phase.EAST){
				phase = Phase.SOUTH; eastIn.red(); southIn.green();
			}
			else if(phase == Phase.SOUTH){
				phase = Phase.WEST; southIn.red(); westIn.green();
			}
			else if(phase == Phase.WEST){
				phase = Phase.NORTH; westIn.red(); northIn.green();
			}
			else{
				System.out.println("could not switch phase");
			}
		}
		else{
			phasecounter++;
		}
		roundcounter++;
		//part two: every CYCLEPHASE call learning loop
		if(roundcounter%Config.CYCLEPHASE == 0){
			//System.out.println("check " + roundcounter);
			northIn.setOccupation(); eastIn.setOccupation(); southIn.setOccupation(); westIn.setOccupation();
			State spr = new State(northIn.getOccupation(), eastIn.getOccupation(), southIn.getOccupation(), westIn.getOccupation());
/*			System.out.println(spr.getNorth()+" "+northIn.getVehicles().size() 
					+" "+ spr.getEast()+" "+eastIn.getVehicles().size()
					+" "+spr.getSouth()+" "+southIn.getVehicles().size()
					+" "+spr.getWest()+" "+westIn.getVehicles().size());
*/			double r = Math.pow(northIn.waitingWithin(northIn.getLength())
							+ eastIn.waitingWithin(eastIn.getLength())
							+ southIn.waitingWithin(southIn.getLength())
							+ westIn.waitingWithin(westIn.getLength()),2);
			if(Config.CONTEXTINFO){ //TODO alternatively use outbound lanes
				int n = 0;
				int e = 0;
				int s = 0;
				int w = 0;
				try{
					n = northIn.getSource().getIn(Direction.SOUTH).waitingWithin(100);
					e = eastIn.getSource().getIn(Direction.WEST).waitingWithin(100);
					s = southIn.getSource().getIn(Direction.NORTH).waitingWithin(100);
					w = westIn.getSource().getIn(Direction.EAST).waitingWithin(100);
				}catch(Exception ex){}
				
				r = Math.pow(northIn.waitingWithin(northIn.getLength()) + n
						+ eastIn.waitingWithin(eastIn.getLength()) + e
						+ southIn.waitingWithin(southIn.getLength()) + s
						+ westIn.waitingWithin(westIn.getLength()) + w,2);;
			}
			int sn = 0;
			int sp = 0;
			int counter = 0;
			for(State st : allStates){
				if(st.equals(s)){
					sn = counter;
				}
				if(st.equals(spr)){
					sp = counter;
				}
				counter++;
			}
			for(PartialModel pm : avpm){
				pm.updateEm(sn, sp, r);
/*				System.out.println("model " + avpm.indexOf(pm) + " Em: " + pm.getEm());
				System.out.println("Phasepolicy: "+pm.getPolicy().getNorth()
						+" "+pm.getPolicy().getEast()
						+" "+pm.getPolicy().getSouth()
						+" "+pm.getPolicy().getWest());
				System.out.println();
*/			}
/*			System.out.println("mcur " + avpm.indexOf(mcur) + " Em: " + mcur.getEm());
			System.out.println("Phasepolicy: "+mcur.getPolicy().getNorth()
					+" "+mcur.getPolicy().getEast()
					+" "+mcur.getPolicy().getSouth()
					+" "+mcur.getPolicy().getWest());
			System.out.println();
*/			double minEm = mcur.getEm();
			PartialModel temp = mcur;
			for(PartialModel pm : avpm){
				if(pm.getEm() < minEm){
					minEm = pm.getEm();
					temp = pm;
				}
			}
			mcur = temp;
			if(mcur.getEm() > Config.LAMBDA){
				mcur = new PartialModel(allStates);
				mcur.setPolicy(spr.optimal());
				avpm.add(mcur); //avpm <- avpm union mcur
			}
			mcur.updateTm(sn, sp);
			mcur.updateRm(sn, r);
			mcur.updateNm(sn);
			s = spr;
			history.add(spr);
		}
	}

	@Override
	public void greedy() {
		int waiting = -1;
		Lane next = null;
		for(Lane l : inLanes){
			if(l.waitingWithin(l.getLength()) > waiting){
				waiting = l.waitingWithin(l.getLength());
				next = l;
			}
		}
		for(Lane l : inLanes){
			l.red();
		}
		next.green();
	}

	public ArrayList<PartialModel> getPartialModels(){
		return avpm;
	}
}

